#include <dspSimulator/instruction/add.h>

bool ADD::checkCondition(std::shared_ptr<DspMachine> dspMachine) {
    if (isCompactInstruction) {
        return true;
    } else {
        return checkStandardCondition(dspMachine);
    }
}

void ADD::loadInstIRIntoJit(llvm::LLVMContext *llvmContext, llvm::Module *module,
                            std::shared_ptr<llvm::IRBuilder<>> irBuilder) {
    llvm::FunctionType *funcType = llvm::FunctionType::get(irBuilder->getVoidTy(), false);
    // --------------------------------- ADD (.unit) src1, src2, dst ---------------------------------------------
    {
        llvm::Function *addFunc = llvm::Function::Create(funcType, llvm::GlobalValue::ExternalLinkage, "ADDKind1",
                                                      module);
        llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(*llvmContext, "entry", addFunc);
        irBuilder->SetInsertPoint(entryBB);

        auto dstRegPtr = getDstLoRegPtr(module, irBuilder);

        auto src1RegPtr = getSrc1LoRegPtr(module, irBuilder);
        auto src1Reg = irBuilder->CreateLoad(src1RegPtr);

        auto src2RegPtr = getSrc2LoRegPtr(module, irBuilder);
        auto src2Reg = irBuilder->CreateLoad(src2RegPtr);

        auto add = irBuilder->CreateAdd(src1Reg, src2Reg);
        irBuilder->CreateStore(add, dstRegPtr);
        irBuilder->CreateRetVoid();
    }

    // --------------------------------- ADD (.unit) scst5, src2, dst---------------------------------------------
    {
        llvm::Function *addFunc = llvm::Function::Create(funcType, llvm::GlobalValue::ExternalLinkage, "ADDKind2",
                                                      module);
        llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(*llvmContext, "entry", addFunc);
        irBuilder->SetInsertPoint(entryBB);

        auto dstRegPtr = getDstLoRegPtr(module, irBuilder);

        auto cst5 = getCstN(module, irBuilder, 5);
        auto scst5 = irBuilder->CreateSExt(cst5, irBuilder->getInt32Ty());

        auto src2RegPtr = getSrc2LoRegPtr(module, irBuilder);
        auto src2Reg = irBuilder->CreateLoad(src2RegPtr);

        auto add = irBuilder->CreateAdd(scst5, src2Reg);
        irBuilder->CreateStore(add, dstRegPtr);
        irBuilder->CreateRetVoid();
    }
}

std::shared_ptr<Instruction> ADD::decode(std::shared_ptr<InstInfo> instInfo, u64 baseCycle) {
    std::shared_ptr<ADD> add;
    auto dspInstInfo = std::dynamic_pointer_cast<DspInstInfo>(instInfo);
    bool isStandardInst = dspInstInfo->isStandardInstruction();
    if (isStandardInst) {
        auto data = vectorToBitSet<32>(dspInstInfo->getBits());
        if (checkBits<11>(data, 2, 0b01000010000) && existStandardCondition(data)) {
            add = std::make_shared<ADD>(dspInstInfo->getAddress(), baseCycle);
            add->creg_z = extractBits<4>(data, 28);
            add->dst = extractBits<5>(data, 23).to_ulong();
            add->src2 = extractBits<5>(data, 18).to_ulong();
            add->src1 = extractBits<5>(data, 13).to_ulong();
            add->s = data[1];
            add->p = data[0];
            if (add->s == 0) {
                add->funcUnit = FuncUnit::D1;
            } else {
                add->funcUnit = FuncUnit::D2;
            }
            add->instFormat = InstFormat::Kind1;
        } else if (checkBits<11>(data, 2, 0b01001010000) && existStandardCondition(data)) {
            add = std::make_shared<ADD>(dspInstInfo->getAddress(), baseCycle);
            add->creg_z = extractBits<4>(data, 28);
            add->dst = extractBits<5>(data, 23).to_ulong();
            add->src2 = extractBits<5>(data, 18).to_ulong();
            add->cst = extractBits<5>(data, 13).to_ulong();
            add->s = data[1];
            add->p = data[0];
            if (add->s == 0) {
                add->funcUnit = FuncUnit::D1;
            } else {
                add->funcUnit = FuncUnit::D2;
            }
            add->instFormat = InstFormat::Kind2;
        } else if (checkBits<10>(data, 2, 0b1010101100) && existStandardCondition(data)) {
            add = std::make_shared<ADD>(dspInstInfo->getAddress(), baseCycle);
            add->creg_z = extractBits<4>(data, 28);
            add->dst = extractBits<5>(data, 23).to_ulong();
            add->src2 = extractBits<5>(data, 18).to_ulong();
            add->src1 = extractBits<5>(data, 13).to_ulong();
            add->x = data[12];
            add->s = data[1];
            add->p = data[0];
            if (add->s == 0) {
                add->funcUnit = FuncUnit::D1;
            } else {
                add->funcUnit = FuncUnit::D2;
            }
            add->instFormat = InstFormat::Kind1;
        } else if (checkBits<10>(data, 2, 0b1010111100) && existStandardCondition(data)) {
            add = std::make_shared<ADD>(dspInstInfo->getAddress(), baseCycle);
            add->creg_z = extractBits<4>(data, 28);
            add->dst = extractBits<5>(data, 23).to_ulong();
            add->src2 = extractBits<5>(data, 18).to_ulong();
            add->cst = extractBits<5>(data, 13).to_ulong();
            add->x = data[12];
            add->s = data[1];
            add->p = data[0];
            if (add->s == 0) {
                add->funcUnit = FuncUnit::D1;
            } else {
                add->funcUnit = FuncUnit::D2;
            }
            add->instFormat = InstFormat::Kind2;
        } else if (checkBits<10>(data, 2, 0b0000011110) && existStandardCondition(data)) {
            add = std::make_shared<ADD>(dspInstInfo->getAddress(), baseCycle);
            add->creg_z = extractBits<4>(data, 28);
            add->dst = extractBits<5>(data, 23).to_ulong();
            add->src2 = extractBits<5>(data, 18).to_ulong();
            add->src1 = extractBits<5>(data, 13).to_ulong();
            add->x = data[12];
            add->s = data[1];
            add->p = data[0];
            if (add->s == 0) {
                add->funcUnit = FuncUnit::L1;
            } else {
                add->funcUnit = FuncUnit::L2;
            }
            add->instFormat = InstFormat::Kind1;
        } else if (checkBits<10>(data, 2, 0b0000010110) && existStandardCondition(data)) {
            add = std::make_shared<ADD>(dspInstInfo->getAddress(), baseCycle);
            add->creg_z = extractBits<4>(data, 28);
            add->dst = extractBits<5>(data, 23).to_ulong();
            add->src2 = extractBits<5>(data, 18).to_ulong();
            add->cst = extractBits<5>(data, 13).to_ulong();
            add->x = data[12];
            add->s = data[1];
            add->p = data[0];
            if (add->s == 0) {
                add->funcUnit = FuncUnit::L1;
            } else {
                add->funcUnit = FuncUnit::L2;
            }
            add->instFormat = InstFormat::Kind2;
        } else if (checkBits<10>(data, 2, 0b0001101000) && existStandardCondition(data)) {
            add = std::make_shared<ADD>(dspInstInfo->getAddress(), baseCycle);
            add->creg_z = extractBits<4>(data, 28);
            add->dst = extractBits<5>(data, 23).to_ulong();
            add->src2 = extractBits<5>(data, 18).to_ulong();
            add->cst = extractBits<5>(data, 13).to_ulong();
            add->x = data[12];
            add->s = data[1];
            add->p = data[0];
            if (add->s == 0) {
                add->funcUnit = FuncUnit::S1;
            } else {
                add->funcUnit = FuncUnit::S2;
            }
            add->instFormat = InstFormat::Kind2;
        } else if (checkBits<10>(data, 2, 0b0001111000) && existStandardCondition(data)) {
            add = std::make_shared<ADD>(dspInstInfo->getAddress(), baseCycle);
            add->creg_z = extractBits<4>(data, 28);
            add->dst = extractBits<5>(data, 23).to_ulong();
            add->src2 = extractBits<5>(data, 18).to_ulong();
            add->src1 = extractBits<5>(data, 13).to_ulong();
            add->x = data[12];
            add->s = data[1];
            add->p = data[0];
            if (add->s == 0) {
                add->funcUnit = FuncUnit::S1;
            } else {
                add->funcUnit = FuncUnit::S2;
            }
            add->instFormat = InstFormat::Kind1;
        }
    } else {
        auto data = vectorToBitSet<16>(dspInstInfo->getBits());
        if (data[10] == 0 && checkBits<3>(data, 1, 0b000) && data[11] == 0 && dspInstInfo->getSAT() == 0) {
            add = std::make_shared<ADD>(dspInstInfo->getAddress(), baseCycle);
            add->isCompactInstruction = true;
            add->compactInstKind = "L3";
            add->src1 = extractBits<3>(data, 13).to_ulong();
            add->x = data[12];
            add->src2 = extractBits<3>(data, 7).to_ulong();
            add->dst = extractBits<3>(data, 4).to_ulong();
            add->s = data[0];
            if (add->s == 0) {
                add->funcUnit = FuncUnit::L1;
            } else {
                add->funcUnit = FuncUnit::L2;
            }
            add->PROT = dspInstInfo->getPROT();
            add->SAT = dspInstInfo->getSAT();
            add->BR = dspInstInfo->getBR();
            add->RS = dspInstInfo->getRS();
            add->DSZ = dspInstInfo->getDSZ();
            add->p = dspInstInfo->getP();
            add->fixUpRegOnCompactInstruction();
            add->instFormat = InstFormat::Kind1;
        } else if (checkBits<3>(data, 1, 0b000) && data[10] == 1) {
            add = std::make_shared<ADD>(dspInstInfo->getAddress(), baseCycle);
            add->isCompactInstruction = true;
            add->compactInstKind = "L3i";
            add->cst = cst3ToScst5(extractBits<3>(data, 13), data[11]).to_ulong();
            add->x = data[12];
            add->src2 = extractBits<3>(data, 7).to_ulong();
            add->dst = extractBits<3>(data, 4).to_ulong();
            add->s = data[0];
            if (add->s == 0) {
                add->funcUnit = FuncUnit::L1;
            } else {
                add->funcUnit = FuncUnit::L2;
            }
            add->PROT = dspInstInfo->getPROT();
            add->SAT = dspInstInfo->getSAT();
            add->BR = dspInstInfo->getBR();
            add->RS = dspInstInfo->getRS();
            add->DSZ = dspInstInfo->getDSZ();
            add->p = dspInstInfo->getP();
            add->fixUpRegOnCompactInstruction();
            add->instFormat = InstFormat::Kind2;
        } else if (checkBits<6>(data, 10, 0b011110) && checkBits<6>(data, 1, 0b110111)) {
            add = std::make_shared<ADD>(dspInstInfo->getAddress(), baseCycle);
            add->isCompactInstruction = true;
            add->compactInstKind = "Sx1";
            add->src2 = add->dst = extractBits<3>(data, 7).to_ulong();
            add->cst = -1;
            add->s = data[0];
            if (add->s == 0) {
                add->funcUnit = FuncUnit::S1;
            } else {
                add->funcUnit = FuncUnit::S2;
            }
            add->PROT = dspInstInfo->getPROT();
            add->SAT = dspInstInfo->getSAT();
            add->BR = dspInstInfo->getBR();
            add->RS = dspInstInfo->getRS();
            add->DSZ = dspInstInfo->getDSZ();
            add->p = dspInstInfo->getP();
            add->fixUpRegOnCompactInstruction();
            add->instFormat = InstFormat::Kind2;
        }
    }
    return add;
}

std::string ADD::toString() const {
    std::string res;
    if (!isCompactInstruction) {
        res += getStandardConditionString() + " ";
    }
    res += m_name + " " + FuncUnitName[static_cast<u32>(funcUnit)] + " ";
    if (instFormat == InstFormat::Kind1) {
        res += getSrc1Reg32Name() + "," + getSrc2Reg32Name() + "," +
               getDstReg32Name();
    } else if (instFormat == InstFormat::Kind2) {
        res += dec2hex(extractBitsAndToInt<5>(cst)) + "," + getSrc2Reg32Name() +
               "," + getDstReg32Name();
    }
    return res;
}

void ADD::executeCustom(std::shared_ptr<TargetMachine> targetMachine, ExecutePhase executePhase) {

}

std::bitset<5> ADD::cst3ToScst5(const std::bitset<3> &cst3, bool sn) {
    std::bitset<5> res;
    for (u32 i = 0; i < 3; i++)
        res[i] = cst3[i];

    if (sn == 0 && cst3 == 0b000)
        res[3] = true;

    if (sn == 1)
        res[4] = res[3] = true;
    return res;
}

std::shared_ptr<DspInstruction> ADD::clone() {
    return std::make_shared<ADD>(*this);
}
